package models;

import java.util.ArrayList;
import java.util.List;
import java.util.Collections;
import java.util.NoSuchElementException;
import java.util.Random;

import models.msgs.Info;

public class Game {
	int numberOfPlayers;
	int activePlayerID;
	int exchangePlayerID;
	int smallBlindID;
	int bigBlindID;
	int dealerPlayerID;
	int startBankroll;
	int roundNumber;
	int smallBlind, bigBlind;
	int pot;

	
	List<String> lista;
	List<ClientWorker> clientworkers;
	List<Player> players;
	Deck deck;

	public Game(int numberOfPlayers, List<ClientWorker> clientworkers, int startBankroll, int smallBlind) {
		lista = new ArrayList<String>();
		players = new ArrayList<Player>(numberOfPlayers);

		for (int i = 0; i < numberOfPlayers; i++) {
			players.add(new Player(startBankroll, i));
		}

		this.numberOfPlayers = numberOfPlayers;
		this.clientworkers = clientworkers;
		this.startBankroll = startBankroll;
		this.smallBlind = smallBlind;
		this.bigBlind = 2*smallBlind;
		deck = new Deck();
		System.out.println("Game created!");
	}
	
	public void initGame() {
		deck.shuffle();
		deal();


		roundNumber = 1;
		long seed = System.nanoTime();
		Random rand = new Random(seed);
		dealerPlayerID = rand.nextInt(numberOfPlayers);
		broadcastMessage("RND " + roundNumber);
		broadcastMessage("DLR " + dealerPlayerID);
		

		smallBlind();
		bigBlind();		
		setActivePlayer();
		broadcastActivePlayer();
	}
	
	void initGameAfterDeal() {
		deck.shuffle();
		
		roundNumber = 1;
		dealerPlayerID = getNextPlayerID(dealerPlayerID);
		broadcastMessage("RND " + roundNumber);
		broadcastMessage("DLR " + dealerPlayerID);
		
		smallBlind();
		bigBlind();		
		setActivePlayer();
		broadcastActivePlayer();
	}
	
	void deal() {
		Card tempCard;
		for (int i = 0; i < players.size(); i++) {
			for (int j = 0; j < 4; j++) {
				tempCard = deck.getCards().remove(deck.getCards().size() - 1);
				players.get(i).hand.cards.add(tempCard);
				
				Table.players.get(i).tell("CRD " + tempCard.toString(),Table.defaultTable);

			}
		}
	}
	
	void returnCardsToDeck() {
		Card returningCard;
		for (int i = 0; i < players.size(); i++) {
			for (int j = 0; j < 4; j++) {
				returningCard = players.get(i).hand.cards.remove(players.get(i).hand.cards.size() -1);
				deck.getCards().add(returningCard);
				clientworkers.get(i).out.println("CRR " + returningCard.toString());
			}
		}
	}
	
	
	boolean nobodyPlayed() {
		for (Player player : players) {
			if (player.playedInRound) {
				return false;
			}
		}
		return true;
	}
	
	boolean everybodyPlayed() {
		for (Player player : players) {
			if (!player.playedInRound && !player.cantPlay()) {
				return false;
			}
		}
		return true;
	}
	
	void setAllNotPlayed() {
		for (Player player : players) {
			player.betInRound = false;
			player.playedInRound = false;
		}
	}


	
	int getNextPlayerID(int playerID) {
		int nextPlayerID = (playerID + 1) % numberOfPlayers;	
		while (players.get(nextPlayerID).cantPlay()) {
			nextPlayerID = (nextPlayerID + 1) % numberOfPlayers;
		}
		return nextPlayerID;
	}
	
	void setActivePlayer() {
		if (nobodyPlayed()) {
			if (roundNumber == 1) {	
				activePlayerID = getNextPlayerID(bigBlindID);
			} else {
				activePlayerID = getNextPlayerID(dealerPlayerID);
			}
		} else {			
			activePlayerID = getNextPlayerID(activePlayerID);
		}
	}
	
	void setExchangePlayer() {
		for (int i = 0 ; i < numberOfPlayers; i++) {
			if (!players.get(i).exchanged && !players.get(i).cantPlay()) {
				exchangePlayerID = i;
				players.get(i).exchanged = true;
				return;
			}
		}
	}
	
	boolean everybodyExchanged() {
		for (Player player : players) {
			if (!player.exchanged && !player.cantPlay()) {
				return false;
			}
		}
		return true;
	}
	
	void setAllNotExchanged() {
		for (Player player : players) {
			player.exchanged = false;
		}
	}
	
	void endOfDeal() {
		setAfterDealState();
		initGameAfterDeal();
		returnCardsToDeck();
		deck.shuffle();
		deal();
	}
	
	void endOfGame() {
		broadcastMessage("EOG " + lastManStandingID() + " " + startBankroll);
		setAfterGameState();
		initGameAfterDeal();
		returnCardsToDeck();
		deck.shuffle();
		deal();
	}
	
	void exchangeMode() {
		if (everybodyExchanged()) {
			setAllNotPlayed();
			setAllNotExchanged();
			roundNumber++;
			if (!checkEndOfDeal()) {
				broadcastRound();
				activePlayerID = getNextPlayerID(dealerPlayerID);
				broadcastActivePlayer();
			}
			if (checkEndOfDeal() && !checkEndOfGame()) {
				handleWinners();
				endOfDeal();			
			}
			if (checkEndOfGame()) {
				handleWinners();
				endOfGame();	
			}
		} else {
			if (roundNumber < 4) {
				setExchangePlayer();
				broadcastMessage("EXC " + exchangePlayerID);
			} else {
				for (Player player : players) {
					player.exchanged = true;
				}
				exchangeMode();
			}
		}
	}
	
	boolean playingPossible() {
		int playersInGame = 0;
		for (Player player : players) {
			if (!player.outOfGame()) {
				playersInGame++;
			}
		}
		if (playersInGame > 1) {
			return true;
		} else {
			return false;
		}	
	}
	
	int lastManStandingID() {
		int id = -1;
		for (Player player : players) {
			if (!player.outOfGame()) {
				id = player.index;
			}
		}
		return id;
	}
	

	
	
	void setAfterGameState() {
		roundNumber = 1;
		pot = 0;

		for (Player player : players) {
			player.setBet(0);
			player.setBankroll(startBankroll);
			player.outOfCoins = false;
			player.didFold = false;
			player.didAllIn = false;		
		}
	}
	
	
	void setAfterDealState() {
		roundNumber = 1;
		
		for (Player player : players) {
			player.didFold = false;
			player.didAllIn = false;
			if (player.getBankroll() == 0) {
				player.outOfCoins = true;
			}
		}
	}
	
	void handleWinners() {
		ArrayList<Hand> hands = new ArrayList<Hand>();
		for (Player player : players) {
			if (!player.didFold) {
				hands.add(player.hand);
			}
		}
		Hand bestHand;
		try {
			bestHand = Collections.max(hands);
		} catch (NoSuchElementException e) {
			broadcastMessage("WIN -1");
			return;
		}
		ArrayList<Integer> winnerIDs = new ArrayList<Integer>();
		for (int i = 0; i < players.size(); i++) {
			if (players.get(i).hand.compareTo(bestHand) == 0 && !players.get(i).didFold) {
				winnerIDs.add(i);
			}
		}
		
		handleChips(winnerIDs);
		
		
		if (winnerIDs.size() > 1) {
			broadcastMessage("WIN -1");
		} else {
			String winnerHand = players.get(winnerIDs.get(0)).hand.toString();
			broadcastMessage("WIN " + winnerIDs.get(0) + " " + winnerHand);
		}
	}

	void afterMove() {
		players.get(activePlayerID).playedInRound = true;
		if (checkEndOfMoves()) {
			exchangeMode();
		} else {
			setActivePlayer();
			broadcastActivePlayer();
		}
	}
	
	boolean checkEndOfMoves () {
		for (Player player : players) {
			boolean cantGoToNextRound = player.getBet() != highestBet() && !player.didFold && !player.didAllIn;
			if (cantGoToNextRound || !everybodyPlayed()) {
				return false;
			}
		}	
		return true;
	}
	
	boolean checkEndOfDeal () {
		if (roundNumber == 5 || aloneNoFold()) {
			return true;
		} else {
			return false;
		}
	}
	
	boolean checkEndOfGame() {
		int playersInGame = 0;
		for (Player player : players) {
			if (!player.outOfGame()) {
				playersInGame++;
			}
		}
		if (playersInGame < 2 && roundNumber == 5) {
			return true;
		} else {
			return false;
		}
		
	}
	
	void handleChips(ArrayList<Integer> winnerIDs) {
		if (winnerIDs.size() == 1) {
			Player winner = players.get(winnerIDs.get(0));
			if (winner.didAllIn) {
				int winnerBankroll = 0;
				for (Player player : players) {
					if (player.getBet() >= winner.getBet()) {
						winnerBankroll += winner.getBet();
					} else {
						winnerBankroll += player.getBet();
					}
				}
				winner.setBankroll(winner.getBankroll() + winnerBankroll);
			} else {
				winner.setBankroll(winner.getBankroll() + pot);
			}
		}
		
		pot = 0;
		for (Player player : players) {
			player.setBet(0);
		}

	}

	void handleAction(String line) {
		switch (line.substring(0, 3)) {
		case "CHK":
			check();
			break;
		case "ALL":
			allIn();
			break;
		case "BET":
			bet(line.substring(4));
			break;
		case "RAI":
			raise(line.substring(4));
			break;
		case "CLL":
			call();
			break;
		case "FLD":
			fold();
			break;
		case "CEN":
			cardExchange(line.substring(4));
			break;
		default:
			lista.add(line);
		}
	}
	
	void smallBlind() {
		smallBlindID = getNextPlayerID(dealerPlayerID);
		while (players.get(smallBlindID).getBankroll() < smallBlind) {
			smallBlindID = getNextPlayerID(smallBlindID);
		} 
		
		players.get(smallBlindID).setBankroll(players.get(smallBlindID).getBankroll() - smallBlind);
		players.get(smallBlindID).setBet(smallBlind);
		pot += smallBlind;
		players.get(smallBlindID).betInRound = true;

		broadcastMessage("SBD " + Integer.toString(smallBlindID) + " " + smallBlind);
	}
	
	void bigBlind() {
		bigBlindID = getNextPlayerID(smallBlindID);
		while (players.get(bigBlindID).getBankroll() < bigBlind) {
			bigBlindID = getNextPlayerID(bigBlindID);
		}

		players.get(bigBlindID).setBankroll(players.get(bigBlindID).getBankroll() - bigBlind);
		players.get(bigBlindID).setBet(bigBlind);
		pot += bigBlind;
		players.get(bigBlindID).betInRound = true;


		broadcastMessage("BBD " + Integer.toString(bigBlindID) + " " + bigBlind);
	}

	boolean everybodyNoBet() {
		for (Player player : players) {
			if (player.betInRound) {
				return false;
			}
		}
		return true;
	}

	int highestBet() {
		int highest = 0;
		for (Player player : players) {
			if (player.getBet() > highest)
				highest = player.getBet();
		}
		return highest;
	}
	
	void allIn() {
		Player activePlayer = players.get(activePlayerID);
		int oldBet = activePlayer.getBet();
		int newBet = activePlayer.getBankroll() + oldBet;
		int difference = newBet - oldBet;
		try {
			if (difference <= 0 || highestBet() <= activePlayer.getBankroll() + oldBet) {
				throw new BadugiException(Integer.toString(newBet));
			}
			activePlayer.setBankroll(0);
			activePlayer.setBet(newBet);
			pot += difference;
			broadcastMessage("ALL " + activePlayerID + " " + newBet);
			activePlayer.didAllIn = true;
			activePlayer.betInRound = true;
			afterMove();
			
		} catch (BadugiException e) {
			clientworkers.get(activePlayerID).out.println("CNT ALL " + e.getMessage());
		}
	}


	void check() {
		if (everybodyNoBet()) {
			broadcastMessage("CHK " + Integer.toString(activePlayerID));
			afterMove();
		} else {
			clientworkers.get(activePlayerID).out.println("CNT CHK ");
		}
	}

	void bet(String chips) {
		Player activePlayer = players.get(activePlayerID);
		int oldBet = activePlayer.getBet();
		int newBet;

		try {
			newBet = Integer.parseInt(chips);
			int difference = newBet - oldBet;
			if (!everybodyNoBet() || difference > activePlayer.getBankroll() || newBet <= oldBet) {
				throw new BadugiException(chips);
			}
			activePlayer.setBankroll(activePlayer.getBankroll() - difference);
			activePlayer.setBet(newBet);
			pot += difference;
			broadcastMessage("BET " + activePlayerID + " " + newBet);
			activePlayer.betInRound = true;
			afterMove();
		} catch (BadugiException e) {
			clientworkers.get(activePlayerID).out.println("CNT BET " + e.getMessage());
		} catch (NumberFormatException e) {
			clientworkers.get(activePlayerID).out.println("CNT BET " + chips);
		}
	}

	void raise(String chips) {
		Player activePlayer = players.get(activePlayerID);
		int oldBet = activePlayer.getBet();
		int newBet;
		
		try {
			newBet = Integer.parseInt(chips);
			int difference = newBet - oldBet;
			if (everybodyNoBet() || oldBet > highestBet() || newBet <= highestBet() || difference > activePlayer.getBankroll()) {
				throw new BadugiException(chips);
			}
			activePlayer.setBankroll(activePlayer.getBankroll() - difference);
			activePlayer.setBet(newBet);
			pot += difference;
			broadcastMessage("RAI " + activePlayerID + " " + newBet);
			activePlayer.betInRound = true;

			afterMove();
		} catch (BadugiException e) {
			clientworkers.get(activePlayerID).out.println("CNT RAI " + e.getMessage());
		} catch (NumberFormatException e) {
			clientworkers.get(activePlayerID).out.println("CNT RAI " + chips);
		}
	}

	void call() {
		Player activePlayer = players.get(activePlayerID);
		int oldBet = activePlayer.getBet();
		int newBet = highestBet();
		int difference = newBet - oldBet;

		try {
			if (everybodyNoBet() || oldBet >= highestBet() || difference > activePlayer.getBankroll()) {
				throw new BadugiException(Integer.toString(newBet));
			}
			activePlayer.setBankroll(activePlayer.getBankroll() - difference);
			activePlayer.setBet(newBet);
			pot += difference;
			broadcastMessage("CLL " + activePlayerID + " " + newBet);
			activePlayer.betInRound = true;

			afterMove();
		} catch (BadugiException e) {
			clientworkers.get(activePlayerID).out.println("CNT CLL " + e.getMessage());
		}
	}
	
	boolean aloneNoFold() {
		int playersWhichNotFold = 0;
		for (Player player : players) {
			if (!player.didFold) {
				playersWhichNotFold++;
			}
		}
		if (playersWhichNotFold == 1) {
			return true;
		} else {
			return false;
		}
	}
	
	void fold() {
		players.get(activePlayerID).didFold = true;
		broadcastMessage("FLD " + Integer.toString(activePlayerID));
		
		if (aloneNoFold()) {
			setAllNotPlayed();
			setAllNotExchanged();
			if (checkEndOfDeal() && !checkEndOfGame()) {
				handleWinners();
				endOfDeal();			
			}
			if (checkEndOfGame()) {
				handleWinners();
				endOfGame();	
			}
			return;
		}

		afterMove();
	}

	void cardExchange(String stringCards) {
		ArrayList<Card> cardsToExchange = new ArrayList<Card>();
		ArrayList<Card> cardsExchanged = new ArrayList<Card>();
		String feedback = "CEN";
		for (int i = 0; i < 4; i++) {
			Card cardToExchange;
			String rank = stringCards.substring(0 + 3 * i, 1 + 3 * i);
			String suit = stringCards.substring(1 + 3 * i, 2 + 3 * i);

			if (Card.getAllowedRanks().contains(rank) && Card.getAllowedSuites().contains(suit)) {
				cardToExchange = new Card(suit, rank);
				cardsToExchange.add(cardToExchange);
			}
		}
		
		for (Card card : cardsToExchange) {
			if (!players.get(exchangePlayerID).hand.cards.contains(card)) {

				clientworkers.get(exchangePlayerID).out.println("CNT CRD ");
				return;
			}
		}


		for (int i = 0; i < cardsToExchange.size(); i++) {
			cardsExchanged.add(deck.getCards().remove(deck.getCards().size() - 1));
		}
		
		for (int i = 0; i < cardsToExchange.size(); i++) {
			players.get(exchangePlayerID).hand.cards.remove(cardsToExchange.get(i));
			players.get(exchangePlayerID).hand.cards.add(cardsExchanged.get(i));
		}

		
		for (Card card : cardsToExchange) {
			deck.getCards().add(card);
		}

		
		for (int i = 0; i < 4; i++) {
			if (i < cardsExchanged.size()) {
				feedback = feedback + " " + cardsExchanged.get(i).toString();
			} else {
				feedback = feedback + " 00";
			}
		}
		

		deck.shuffle();
		broadcastMessage("CEA " + Integer.toString(exchangePlayerID) + " " + Integer.toString(cardsExchanged.size()));
		System.out.println(feedback);
		clientworkers.get(exchangePlayerID).out.println(feedback);
		
		exchangeMode();
	}
	
	void broadcastRound() {
		if (roundNumber < 5) {
			broadcastMessage("RND " + roundNumber);
		} else {
			broadcastMessage("RND 1");
		}
	}

	void broadcastActivePlayer() {
		broadcastMessage("ACT " + Integer.toString(activePlayerID));
	}

	public void broadcastStartInfo() {
		broadcastMessage("NUM " + Integer.toString(numberOfPlayers));
		broadcastMessage("BRL " + Integer.toString(startBankroll));
	}

	void broadcastMessage(String str) {
		Table.notifyAll(new Info(str, "Table"));
	}
}
